import 'dart:math';
import 'dart:ui';

import 'package:flutter/material.dart';

class EditExample extends StatefulWidget {
  const EditExample({super.key});

  @override
  State<EditExample> createState() => _EditExampleState();
}

class _EditExampleState extends State<EditExample> {
  List<Action> actions = [];
  List<List<Action>> redoActions = [];

  Map<String, ValueGetter<PointerAction>> get map =>
      <String, ValueGetter<PointerAction>>{
        'Line': () => LineAction(),
        'ArrowLine': () => ArrowLineAction(),
        'Rectangle': () => RectangleAction(),
        'Oval': () => OvalAction(),
        'Points': () => PointsAction(),
      };

  late String currentAction = map.keys.first;

  Picture? picture;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Edit Example'),
        actions: [
          IconButton(
            icon: const Icon(Icons.preview),
            onPressed: () async {
              final image = await picture?.toImage(1000, 1000);
              final byteData =
                  await image?.toByteData(format: ImageByteFormat.png);

              final bytes = byteData?.buffer.asUint8List();
              if (bytes == null) {
                return;
              }
              // show preview in dialog
              // ignore: use_build_context_synchronously
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  content: SizedBox(
                    width: MediaQuery.of(context).size.width / 2,
                    height: MediaQuery.of(context).size.height / 2,
                    child: Image.memory(bytes),
                  ),
                ),
              );
            },
          )
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: Listener(
              onPointerDown: (event) {
                final action = map[currentAction]!();
                if (action is DrawAction) {
                  action.storkeWidth = storkeWidth;
                  action.color = color;
                  action.paintingStyle =
                      filled ? PaintingStyle.fill : PaintingStyle.stroke;
                }
                redoActions.clear();
                action.onPointerDown(event);

                for (final action in actions) {
                  action.onActionEnd();
                }

                actions.add(action);

                setState(() {});
              },
              onPointerMove: (event) {
                final action = actions.last;
                if (action is PointerAction) {
                  action.onPointerMove(event);
                }
                setState(() {});
              },
              child: SizedBox(
                width: double.infinity,
                height: double.infinity,
                child: RepaintBoundary(
                  child: CustomPaint(
                    painter: EditorPainter(actions, (picture) {
                      this.picture = picture;
                    }),
                  ),
                ),
              ),
            ),
          ),
          _buildOptions(),
        ],
      ),
    );
  }

  var storkeWidth = 2.0;
  Color color = Colors.red;
  var filled = false;

  Widget _buildOptions() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          CheckboxListTile(
            value: filled,
            onChanged: (v) {
              filled = v == true;
              setState(() {});
            },
            title: const Text('filled'),
          ),
          Row(
            children: [
              const Text('line width:'),
              Expanded(
                child: Slider(
                  value: storkeWidth,
                  min: 1,
                  max: 20,
                  divisions: 190,
                  label: storkeWidth.toStringAsFixed(1),
                  onChanged: (value) {
                    storkeWidth = value;
                    setState(() {});
                  },
                ),
              ),
            ],
          ),
          Row(
            children: [
              const Text('Color argb:'),
              Expanded(
                child: Slider(
                  value: color.alpha.toDouble(),
                  min: 0,
                  max: 255,
                  divisions: 255,
                  label: color.alpha.toString(),
                  onChanged: (value) {
                    color = Color.fromARGB(
                        value.toInt(), color.red, color.green, color.blue);
                    setState(() {});
                  },
                ),
              ),
              Expanded(
                child: Slider(
                  value: color.red.toDouble(),
                  min: 0,
                  max: 255,
                  divisions: 255,
                  label: color.red.toString(),
                  onChanged: (value) {
                    color = Color.fromARGB(
                        color.alpha, value.toInt(), color.green, color.blue);
                    setState(() {});
                  },
                ),
              ),
              Expanded(
                child: Slider(
                  value: color.green.toDouble(),
                  min: 0,
                  max: 255,
                  divisions: 255,
                  label: color.green.toString(),
                  onChanged: (value) {
                    color = Color.fromARGB(
                        color.alpha, color.red, value.toInt(), color.blue);
                    setState(() {});
                  },
                ),
              ),
              Expanded(
                child: Slider(
                  value: color.blue.toDouble(),
                  min: 0,
                  max: 255,
                  divisions: 255,
                  label: color.blue.toString(),
                  onChanged: (value) {
                    color = Color.fromARGB(
                        color.alpha, color.red, color.green, value.toInt());
                    setState(() {});
                  },
                ),
              ),
              SizedBox.square(
                dimension: 20,
                child: Container(
                  color: color,
                ),
              ),
            ],
          ),
          Row(
            children: [
              TextButton.icon(
                label: const Text('Undo'),
                icon: const Icon(Icons.undo),
                onPressed: actions.isEmpty
                    ? null
                    : () {
                        if (actions.isEmpty) {
                          return;
                        }
                        final action = actions.removeLast();
                        redoActions.add([action]);
                        setState(() {});
                      },
              ),
              TextButton.icon(
                label: const Text('Redo'),
                icon: const Icon(Icons.redo),
                onPressed: redoActions.isEmpty
                    ? null
                    : () {
                        if (redoActions.isEmpty) {
                          return;
                        }
                        final action = redoActions.removeLast();
                        actions.addAll(action);
                        setState(() {});
                      },
              ),
              TextButton.icon(
                label: const Text('Clear'),
                icon: const Icon(Icons.clear),
                onPressed: actions.isEmpty
                    ? null
                    : () {
                        // redoActions.add(actions.toList());
                        redoActions.clear();
                        actions.clear();
                        setState(() {});
                      },
              ),
            ].map((child) => Expanded(child: child)).toList(),
          ),
          Row(
            children: [
              ...map.keys.map((key) {
                return Expanded(
                  child: TextButton(
                    onPressed: () {
                      setState(() {
                        currentAction = key;
                      });
                    },
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        IgnorePointer(
                          child: Radio<String>(
                            value: key,
                            onChanged: (v) {},
                            groupValue: currentAction,
                          ),
                        ),
                        Flexible(
                          child: Container(
                            height: 32,
                            alignment: Alignment.center,
                            child: Text(key),
                          ),
                        ),
                      ],
                    ),
                  ),
                );
              }),
            ],
          ),
        ],
      ),
    );
  }
}

abstract class Action {
  void paint(Canvas canvas, Size size);

  void onActionEnd() {}
}

abstract class PointerAction extends Action {
  void onPointerDown(PointerDownEvent event);

  void onPointerMove(PointerMoveEvent event);
}

/// 画图的Action
abstract class DrawAction extends PointerAction {
  List<Offset> points = [];

  double storkeWidth = 2.0;
  PaintingStyle paintingStyle = PaintingStyle.stroke;
  Color color = Colors.red;

  Paint get _paint => Paint()
    ..color = color
    ..strokeWidth = storkeWidth
    ..style = paintingStyle
    ..isAntiAlias = true;

  void addPoint(Offset point) {
    points.add(point);
  }

  @override
  void paint(Canvas canvas, Size size) {
    if (points.length < 2) {
      return;
    }
    onPaint(canvas, size);
  }

  void onPaint(Canvas canvas, Size size);
}

// 画线的Action
class LineAction extends DrawAction {
  @override
  void onPaint(Canvas canvas, Size size) {
    for (var i = 0; i < points.length - 1; i++) {
      canvas.drawLine(points[i], points[i + 1], _paint);
    }
  }

  @override
  void onPointerDown(PointerDownEvent event) {
    addPoint(event.localPosition);
  }

  @override
  void onPointerMove(PointerMoveEvent event) {
    if (points.length == 1) {
      addPoint(event.localPosition);
    } else {
      points[points.length - 1] = event.localPosition;
    }
  }
}

/// 带箭头的Action
class ArrowLineAction extends LineAction {
  @override
  void onPaint(Canvas canvas, Size size) {
    final paint = _paint;

    // final isStroke = paint.style == PaintingStyle.stroke;

    for (var i = 0; i < points.length - 1; i++) {
      final p1 = points[i];
      final p2 = points[i + 1];
      final angle = atan2(p2.dy - p1.dy, p2.dx - p1.dx);
      final arrowLength = paint.strokeWidth * 6;
      const arrowAngle = pi / 6;
      final p3 = Offset(
        p2.dx - arrowLength * cos(angle + arrowAngle),
        p2.dy - arrowLength * sin(angle + arrowAngle),
      );
      final p4 = Offset(
        p2.dx - arrowLength * cos(angle - arrowAngle),
        p2.dy - arrowLength * sin(angle - arrowAngle),
      );

      final path = Path()
        ..moveTo(p3.dx, p3.dy)
        ..lineTo(p2.dx, p2.dy)
        ..lineTo(p4.dx, p4.dy);
      canvas.drawLine(p1, p2, paint);
      canvas.drawPath(path, paint);
    }
  }
}

/// 圆形Action
class OvalAction extends DrawAction {
  @override
  void onPaint(Canvas canvas, Size size) {
    // 画一个空心椭圆
    final rect = Rect.fromPoints(points[0], points[1]);
    canvas.drawOval(rect, _paint);
  }

  @override
  void onPointerDown(PointerDownEvent event) {
    addPoint(event.localPosition);
  }

  @override
  void onPointerMove(PointerMoveEvent event) {
    if (points.length == 1) {
      addPoint(event.localPosition);
    } else {
      points[points.length - 1] = event.localPosition;
    }
  }
}

/// 矩形Action
class RectangleAction extends DrawAction {
  @override
  void onPaint(Canvas canvas, Size size) {
    final rect = Rect.fromPoints(points[0], points[1]);
    canvas.drawRect(rect, _paint);
  }

  @override
  void onPointerDown(PointerDownEvent event) {
    addPoint(event.localPosition);
  }

  @override
  void onPointerMove(PointerMoveEvent event) {
    if (points.length == 1) {
      addPoint(event.localPosition);
    } else {
      points[points.length - 1] = event.localPosition;
    }
  }
}

/// 点的Action
class PointsAction extends DrawAction {
  @override
  Paint get _paint => super._paint..style = PaintingStyle.stroke;

  @override
  void onPaint(Canvas canvas, Size size) {
    // 以每个点来生成平滑的线
    final path = Path();
    path.moveTo(points[0].dx, points[0].dy);
    for (var i = 1; i < points.length - 2; i++) {
      final p1 = points[i];
      final p2 = points[i + 1];
      path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);
    }
    canvas.drawPath(path, _paint);
  }

  @override
  void onPointerDown(PointerDownEvent event) {
    addPoint(event.localPosition);
  }

  @override
  void onPointerMove(PointerMoveEvent event) {
    addPoint(event.localPosition);
  }
}

/// 文字的Action
class TextAction extends PointerAction {
  List<Offset> offsets = [];

  bool hasFocus = true;

  @override
  void paint(Canvas canvas, Size size) {
    if (offsets.length < 2) {
      return;
    }
    if (hasFocus) {
      // draw dashed line
      final paint = Paint()
        ..color = Colors.red
        ..strokeWidth = 1.0
        ..style = PaintingStyle.stroke
        ..isAntiAlias = true
        ..strokeCap = StrokeCap.round
        ..strokeJoin = StrokeJoin.round;

      // 画虚线，使用 Path, PathEffect
      final p1 = offsets[0];
      final p3 = offsets[1];
      final rect = Rect.fromPoints(p1, p3);

      final path = Path();
      _addDashLineWithRect(path, rect);

      canvas.drawPath(path, paint);
    }
  }

  @override
  void onActionEnd() {
    hasFocus = false;
  }

  void _addDashLineWithRect(Path path, Rect rect) {
    final p1 = rect.topLeft;
    final p2 = rect.topRight;
    final p3 = rect.bottomRight;
    final p4 = rect.bottomLeft;

    path.moveTo(p1.dx, p1.dy);
    _addDashLineWithPoints(path, p1, p2);
    _addDashLineWithPoints(path, p2, p3);
    _addDashLineWithPoints(path, p3, p4);
    _addDashLineWithPoints(path, p4, p1);
  }

  void _addDashLineWithPoints(Path path, Offset p1, Offset p2) {
    const dashWidth = 5.0;
    const dashSpace = 5.0;

    final dx = p2.dx - p1.dx;
    final dy = p2.dy - p1.dy;
    final length = sqrt(dx * dx + dy * dy);
    final angle = atan2(dy, dx);

    final dashCount = (length / (dashWidth + dashSpace)).ceil();
    final dashLength = length / dashCount;

    for (var i = 0; i < dashCount; i++) {
      final x = p1.dx + dashLength * i * cos(angle);
      final y = p1.dy + dashLength * i * sin(angle);
      path.lineTo(x, y);
      path.moveTo(x + dashWidth * cos(angle), y + dashWidth * sin(angle));
    }
  }

  @override
  void onPointerDown(PointerDownEvent event) {
    offsets.add(event.localPosition);
  }

  @override
  void onPointerMove(PointerMoveEvent event) {
    if (offsets.length == 1) {
      offsets.add(event.localPosition);
    } else {
      offsets[1] = event.localPosition;
    }
  }
}

/// 负责绘制的CustomPainter
class EditorPainter extends CustomPainter {
  EditorPainter(this.actions, this.onPaintEnd);

  final List<Action> actions;
  final ValueChanged<Picture?>? onPaintEnd;

  bool get neetRecord => onPaintEnd != null;

  @override
  void paint(Canvas canvas, Size size) {
    PictureRecorder? recorder;
    Canvas? recordCanvas;
    if (neetRecord) {
      recorder = PictureRecorder();
      recordCanvas = Canvas(recorder);
    }
    for (final action in actions) {
      action.paint(canvas, size);
      if (recordCanvas != null) {
        action.paint(recordCanvas, size);
      }
    }
    final picture = recorder?.endRecording();
    onPaintEnd?.call(picture);
  }

  @override
  bool shouldRepaint(covariant EditorPainter oldDelegate) => true;
}
